package ubadbtools.deadlockAnalyzer;

import java.util.*;

import ubadbtools.deadlockAnalyzer.common.Schedule;
import ubadbtools.deadlockAnalyzer.grafo.*;
import ubadbtools.deadlockAnalyzer.results.ValidationResult;
import ubadbtools.deadlockAnalyzer.scheduleRecords.ScheduleRecord;

public class DeadlockValidation {
	public static ValidationResult executeFor(Schedule aSchedule,
			ScheduleRecord recordConflict) {
		return new DeadlockValidation(aSchedule, recordConflict).execute();
	}

	public static ValidationResult executeFor(Schedule aSchedule) {
		return new DeadlockValidation(aSchedule).execute();
	}

	private Schedule schedule;
	private ScheduleRecord record;
	private boolean preventDeadlock;

	public DeadlockValidation(Schedule aSchedule, ScheduleRecord recordConflict) {
		record = recordConflict;
		schedule = aSchedule;
		preventDeadlock = true;
	}

	public DeadlockValidation(Schedule aSchedule) {
		schedule = aSchedule;
		preventDeadlock = false;
	}

	private ValidationResult execute() {
		boolean passed = true;
		String message = "";
		try {
			if (preventDeadlock)
				searchForPossibleDeadlockTimestamp();
			if (!preventDeadlock)
				searchForPossibleDeadlockGraph();
		} catch (ValidationException e) {
			message = e.getMessage();
			passed = false;
		}

		return new ValidationResult(passed, message);
	}

	private void searchForPossibleDeadlockTimestamp()
			throws ValidationException {
		for (ScheduleRecord rec : schedule.getScheduleRecords()) {
			if (rec.isLock() && rec.getItem() == record.getItem())
			// De todos los pasos de tipo Lock solo me fijo en los que fueron
			// hechos
			// sobre el item sobre el que se esta haciendo RequestLock
			// falta ver cual tiene el timestamp menor
			{
				if (!commited(rec.getTransaction())
						&& !aborted(rec.getTransaction())
						&& !unlocked(rec.getTransaction(), rec.getItem())) {
					Map<String, Integer> timeStamps = schedule.getTimestamps();
					if (timeStamps.get(rec.getTransaction()) < timeStamps
							.get(record.getTransaction()))
						// Se puede producir un deadlock
						fail("DeadlockPrevention: Hacer un Rlock sobre "
								+ record.getItem()
								+ " puede provocar un deadlock");
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void searchForPossibleDeadlockGraph() throws ValidationException {
		Grafo diGrafoDeRecursos = new Grafo(true); // Crea un digrafo vac�o
		String recurso;
		String transaccion;
		IteradorNodos it;
		boolean found, found2;
		int nodo = 0, nodoDestino = 0, nodoOrigen = 0;
		List<ScheduleRecord> listOfRecords = new ArrayList<ScheduleRecord>(
				schedule.getScheduleRecords());
		// listOfRecords.add(record); //agrego al final el �ltimo registro

		// Construimos el digrafo
		// A cada nodo del mismo asociaremos un ArrayList de Strings
		// En el que el primer elemento es el nombre de la transacci�n
		// y los elementos siguientes son los recursos en uso
		// por parte de dicha transacci�n
		for (ScheduleRecord rec : listOfRecords) {
			ArrayList payload;
			transaccion = rec.getTransaction();
			if (rec.isLock()) {
				recurso = rec.getItem();
				it = new IteradorNodos(diGrafoDeRecursos);
				found = false; // encontramos la transacción actual
				while (it.hayMas()) {
					nodo = it.obtenerNodo();
					payload = (ArrayList) diGrafoDeRecursos
							.obtenerDatosAsociadosNodo(nodo);
					if (payload.get(0) == transaccion)
						found = true;
				}

				if (found) {
					// Hay que agregar el recurso
					payload = (ArrayList) diGrafoDeRecursos
							.obtenerDatosAsociadosNodo(nodo);
					payload.add(recurso);
					diGrafoDeRecursos.asociarDatosNodo(nodo, payload);
				} else {
					nodo = diGrafoDeRecursos.cantidadNodos() + 1;
					diGrafoDeRecursos.agregarNodo(nodo);
					payload = new ArrayList();
					payload.add(0, transaccion);
					payload.add(recurso);
					diGrafoDeRecursos.asociarDatosNodo(nodo, payload);
				}
			}

			if (rec.isUnlock()) {
				recurso = rec.getItem();
				it = new IteradorNodos(diGrafoDeRecursos);
				found = false; // encontramos la transacción actual
				while (it.hayMas() && !found) {
					nodo = it.obtenerNodo();
					payload = (ArrayList) diGrafoDeRecursos
							.obtenerDatosAsociadosNodo(nodo);
					if (payload.get(0) == transaccion) {
						found = true;
						// Eliminar el recurso utilizado
						payload.remove(rec.getItem());
					}
					// remover ejes entrantes de transacciones que necesitan
					// este recurso liberado
					IteradorAntecesores incoming = new IteradorAntecesores(
							diGrafoDeRecursos, nodo);
					while (incoming.hayMas()) {
						nodoDestino = incoming.obtenerNodo();
						payload = (ArrayList) diGrafoDeRecursos
								.obtenerDatosAsociadosNodo(nodoDestino);
						if (payload.subList(1, payload.size())
								.contains(recurso))
							diGrafoDeRecursos.eliminarEje(nodoDestino, nodo);
					}

				}
			}

			if (rec.isCommit() || rec.isAbort()) {
				it = new IteradorNodos(diGrafoDeRecursos);
				found = false; // encontramos la transacción actual
				while (it.hayMas() && !found) {
					nodo = it.obtenerNodo();
					payload = (ArrayList) diGrafoDeRecursos
							.obtenerDatosAsociadosNodo(nodo);
					if (payload.get(0) == transaccion) {
						found = true;
						// Eliminar todos los recursos utilizados (forzar
						// Unlock?)
						payload.retainAll(payload.subList(0, 0));
						// remover todos los ejes entrantes
						IteradorAntecesores incoming = new IteradorAntecesores(
								diGrafoDeRecursos, nodo);
						while (incoming.hayMas()) {
							diGrafoDeRecursos.eliminarEje(incoming
									.obtenerNodo(), nodo);

						}
					}
				}
			}

			if (rec.isRequestLock()) {
				recurso = rec.getItem();
				it = new IteradorNodos(diGrafoDeRecursos);
				found = false; // encontramos la transacción actual
				found2 = false; // encontramos otra transacción que ya usa el
								// recurso
				while (it.hayMas()) {
					nodo = it.obtenerNodo();
					payload = (ArrayList) diGrafoDeRecursos
							.obtenerDatosAsociadosNodo(nodo);
					if (payload.get(0) == transaccion) {
						found = true;
						nodoOrigen = nodo;
					} else {
						if (payload.subList(1, payload.size())
								.contains(recurso)) {
							found2 = true;
							nodoDestino = nodo;
						}
					}
				}

				if (found && found2) {
					diGrafoDeRecursos.agregarEje(nodoOrigen, nodoDestino);
					if (diGrafoDeRecursos.tieneCicloEmpezandoEn(nodo)) {
						fail("La transacci�n "+ transaccion + "genera deadlock al tomar el recurso " + recurso+".");
					}
				} else {
					if (!found && found2) {
						// Hay que agregar esta transacción
						// y un eje de nodo a nodo2
						nodoOrigen = diGrafoDeRecursos.cantidadNodos() + 1;
						diGrafoDeRecursos.agregarNodo(nodoOrigen);
						payload = new ArrayList();
						payload.add(0, nodoOrigen);
						diGrafoDeRecursos.agregarEje(nodoOrigen, nodoDestino);
						if (diGrafoDeRecursos.tieneCicloEmpezandoEn(nodoOrigen)) {
							fail(transaccion + " puede generar un deadlock al tomar " + recurso);
						}
					} else {
						if (found && !found2) {
							// no pasa nada todo legal!
							continue;
						} else {
							if (!found && !found2) {
								// no pasa nada todo legal!
								continue;
							}
						}
					}
				}
			}
		}
	}

	public ScheduleRecord buscarStart(String trans) {
		ScheduleRecord ret = null;
		List<ScheduleRecord> recs = schedule.getScheduleRecords();
		for (ScheduleRecord rec : recs) {
			// De todos los pasos anteriores selecciono solo los de tipo Start
			if (rec.isStart() && rec.getTransaction() == trans)
				ret = rec;
		}
		return ret;
	}

	private boolean commited(String trans) {
		boolean ret = false;
		List<ScheduleRecord> recs = schedule.getScheduleRecords();
		for (ScheduleRecord rec : recs) {
			// De todos los pasos anteriores selecciono solo los de tipo Commit
			if (rec.isCommit() && rec.getTransaction() == trans)
				ret = true;
		}

		return ret;
	}

	private boolean aborted(String trans) {
		boolean ret = false;
		List<ScheduleRecord> recs = schedule.getScheduleRecords();
		for (ScheduleRecord rec : recs) {
			// De todos los pasos anteriores selecciono solo los de tipo Abort
			if (rec.isAbort() && rec.getTransaction() == trans)
				ret = true;
		}

		return ret;
	}

	private boolean unlocked(String trans, String item)
			throws ValidationException {
		boolean ret = false;
		List<ScheduleRecord> recs = schedule.getScheduleRecords();
		for (ScheduleRecord rec : recs) {
			// De todos los pasos anteriores selecciono solo los de tipo Unlock
			if (rec.isUnlock() && rec.getTransaction() == trans
					&& item == rec.getItem())
				ret = true;
		}

		return ret;
	}

	private void fail(String message) throws ValidationException {
		throw new ValidationException(message);
	}
}
